package redis

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gitee.com/xfrm/middleware/internal/middlewareconf"
	"gitee.com/xfrm/middleware/xcache/constants"
	"gitee.com/xfrm/middleware/xlog"
	"gitee.com/xfrm/middleware/xtrace"

	"github.com/go-redis/redis"
)

const (
	traceComponent = "xcache"
)

var RedisNil = fmt.Sprintf("redis: nil")

type Client struct {
	client    *redis.Client
	namespace string
	opts      *options
	cluster   string
}

func (c *Client) Cluster() string {
	if c == nil {
		return ""
	}

	return c.cluster
}

func NewClient(ctx context.Context, namespace string, wrapper string) (*Client, error) {
	fun := "NewClient -->"

	middlewareConfig, err := middlewareconf.GetMiddlewareConfig()
	if err != nil {
		xlog.Errorf(ctx, "%s middlewareconf err: %v", fun, err)
		return nil, err
	}

	config, err := middlewareConfig.GetCacheConfig(ctx, namespace, middlewareconf.MiddlewareTypeNewCache)
	if err != nil {
		return nil, err
	}

	client := redis.NewClient(&redis.Options{
		Addr:         config.Addr,
		DialTimeout:  3 * config.Timeout,
		ReadTimeout:  config.Timeout,
		WriteTimeout: config.Timeout,
		PoolSize:     config.PoolSize,
		PoolTimeout:  2 * config.Timeout,
		Password:     config.Password,
	})

	pong, err := client.Ping().Result()
	if err != nil {
		xlog.Errorf(ctx, "%s ping:%s err:%v", fun, pong, err)
	}
	opts := &options{
		wrapper:    wrapper,
		noFixKey:   false,
		useWrapper: config.UseWrapper,
	}
	return &Client{
		client:    client,
		namespace: namespace,
		opts:      opts,
		cluster:   config.Cluster,
	}, err
}

func NewClientWithOptions(ctx context.Context, namespace string, opts ...Option) (*Client, error) {
	fun := "NewClient -->"

	middlewareConfig, err := middlewareconf.GetMiddlewareConfig()
	if err != nil {
		xlog.Errorf(ctx, "%s middlewareconf err: %v", fun, err)
		return nil, err
	}

	config, err := middlewareConfig.GetCacheConfig(ctx, namespace, middlewareconf.MiddlewareTypeNewCache)
	if err != nil {
		return nil, err
	}

	client := redis.NewClient(&redis.Options{
		Addr:         config.Addr,
		DialTimeout:  3 * config.Timeout,
		ReadTimeout:  config.Timeout,
		WriteTimeout: config.Timeout,
		PoolSize:     config.PoolSize,
		PoolTimeout:  2 * config.Timeout,
		Password:     config.Password,
	})

	pong, err := client.Ping().Result()
	if err != nil {
		xlog.Errorf(ctx, "%s ping:%s err:%v", fun, pong, err)
	}
	opt := &options{
		wrapper:    constants.DefaultRedisWrapper,
		noFixKey:   false,
		useWrapper: config.UseWrapper,
	}
	for _, o := range opts {
		o.apply(opt)
	}
	return &Client{
		namespace: namespace,
		opts:      opt,
		client:    client,
		cluster:   config.Cluster,
	}, err
}

func NewDefaultClient(ctx context.Context, namespace, addr, wrapper string, poolSize int, useWrapper bool, timeout time.Duration) (*Client, error) {
	fun := "NewDefaultClient -->"

	client := redis.NewClient(&redis.Options{
		Addr:         addr,
		DialTimeout:  3 * timeout,
		ReadTimeout:  timeout,
		WriteTimeout: timeout,
		PoolSize:     poolSize,
		PoolTimeout:  2 * timeout,
	})

	pong, err := client.Ping().Result()
	if err != nil {
		xlog.Errorf(ctx, "%s Ping: %s err: %v", fun, pong, err)
	}

	return &Client{
		client:    client,
		namespace: namespace,
		opts: &options{
			wrapper:    wrapper,
			noFixKey:   false,
			useWrapper: useWrapper,
		},
		cluster: middlewareconf.FetchCodisClusterName(addr),
	}, err
}

func (m *Client) fixKey(key string) string {
	if m.opts.noFixKey {
		return key
	}
	parts := []string{
		m.namespace,
		m.opts.wrapper,
		key,
	}
	if !m.opts.useWrapper {
		parts = []string{
			m.namespace,
			key,
		}
	}
	return strings.Join(parts, ".")
}

func redisCmd(cmder redis.Cmder) string {
	if cmder == nil {
		return ""
	}

	var ss []string
	for _, arg := range cmder.Args() {
		ss = append(ss, fmt.Sprint(arg))
	}
	return strings.Join(ss, " ")
}

func (m *Client) setSpanTags(ctx context.Context, cmder redis.Cmder) {
	if span := xtrace.SpanFromContext(ctx); span != nil {
		span.SetTag(xtrace.TagComponent, traceComponent)
		span.SetTag(xtrace.TagDBType, xtrace.DBTypeRedis)
		span.SetTag(xtrace.TagPalfishDBCluster, m.cluster)
		span.SetTag(xtrace.TagSpanKind, xtrace.SpanKindClient)
		span.SetTag(xtrace.TagDBStatement, redisCmd(cmder))
	}
}

func (m *Client) Do(ctx context.Context, cmd string, keys []string, otherArgs ...interface{}) *redis.Cmd {
	var fixedKeys []string
	for _, v := range keys {
		fixedKeys = append(fixedKeys, m.fixKey(v))
	}

	args := []interface{}{cmd}
	for _, key := range fixedKeys {
		args = append(args, key)
	}
	args = append(args, otherArgs...)

	rcmd := m.client.Do(args...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Get(ctx context.Context, key string) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.Get(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) MGet(ctx context.Context, keys ...string) *redis.SliceCmd {
	var fixKeys = make([]string, 0, len(keys))
	for _, v := range keys {
		fixKeys = append(fixKeys, m.fixKey(v))
	}
	rcmd := m.client.MGet(fixKeys...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd {
	k := m.fixKey(key)
	rcmd := m.client.Set(k, value, expiration)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) MSet(ctx context.Context, pairs ...interface{}) *redis.StatusCmd {
	var fixPairs = make([]interface{}, 0, len(pairs))
	for k, v := range pairs {
		if (k & 1) == 0 {
			key := m.fixKey(v.(string))
			fixPairs = append(fixPairs, key)
		} else {
			fixPairs = append(fixPairs, v)
		}
	}
	rcmd := m.client.MSet(fixPairs...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) GetBit(ctx context.Context, key string, offset int64) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.GetBit(k, offset)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SetBit(ctx context.Context, key string, offset int64, value int) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.SetBit(k, offset, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Exists(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.Exists(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Type(ctx context.Context, key string) *redis.StatusCmd {
	k := m.fixKey(key)
	rcmd := m.client.Type(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Del(ctx context.Context, keys ...string) *redis.IntCmd {
	var tkeys []string
	for _, key := range keys {
		tkeys = append(tkeys, m.fixKey(key))
	}

	rcmd := m.client.Del(tkeys...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Append(ctx context.Context, key, value string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.Append(k, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Expire(ctx context.Context, key string, expiration time.Duration) *redis.BoolCmd {
	k := m.fixKey(key)
	rcmd := m.client.Expire(k, expiration)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Incr(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.Incr(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) IncrBy(ctx context.Context, key string, value int64) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.IncrBy(k, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Decr(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.Decr(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) DecrBy(ctx context.Context, key string, value int64) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.DecrBy(k, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd {
	k := m.fixKey(key)
	rcmd := m.client.SetNX(k, value, expiration)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HSet(ctx context.Context, key string, field string, value interface{}) *redis.BoolCmd {
	k := m.fixKey(key)
	rcmd := m.client.HSet(k, field, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.HDel(k, fields...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HExists(ctx context.Context, key string, field string) *redis.BoolCmd {
	k := m.fixKey(key)
	rcmd := m.client.HExists(k, field)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HGet(ctx context.Context, key string, field string) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.HGet(k, field)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd {
	k := m.fixKey(key)
	rcmd := m.client.HGetAll(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HIncrBy(ctx context.Context, key string, field string, incr int64) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.HIncrBy(k, field, incr)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HIncrByFloat(ctx context.Context, key string, field string, incr float64) *redis.FloatCmd {
	k := m.fixKey(key)
	rcmd := m.client.HIncrByFloat(k, field, incr)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HKeys(ctx context.Context, key string) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.HKeys(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HLen(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.HLen(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HMGet(ctx context.Context, key string, fields ...string) *redis.SliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.HMGet(k, fields...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HMSet(ctx context.Context, key string, fields map[string]interface{}) *redis.StatusCmd {
	k := m.fixKey(key)
	rcmd := m.client.HMSet(k, fields)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HSetNX(ctx context.Context, key string, field string, val interface{}) *redis.BoolCmd {
	k := m.fixKey(key)
	rcmd := m.client.HSetNX(k, field, val)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HVals(ctx context.Context, key string) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.HVals(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd {
	k := m.fixKey(key)
	rcmd := m.client.HScan(k, cursor, match, count)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZAdd(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZAdd(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZAddNX(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZAddNX(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZAddNXCh(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZAddNXCh(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZAddXX(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZAddXX(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZAddXXCh(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZAddXXCh(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZAddCh(ctx context.Context, key string, members ...redis.Z) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZAddCh(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZCard(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZCard(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZCount(ctx context.Context, key, min, max string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZCount(k, min, max)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRange(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRangeByLex(ctx context.Context, key string, by redis.ZRangeBy) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRangeByLex(k, by)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRangeByScore(ctx context.Context, key string, by redis.ZRangeBy) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRangeByScore(k, by)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRangeByScoreWithScores(ctx context.Context, key string, by redis.ZRangeBy) *redis.ZSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRangeByScoreWithScores(k, by)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *redis.ZSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRangeWithScores(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRevRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRevRange(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *redis.ZSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRevRangeWithScores(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRevRangeByScoreWithScores(ctx context.Context, key string, by redis.ZRangeBy) *redis.ZSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRevRangeByScoreWithScores(k, by)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRevRangeByScore(ctx context.Context, key string, by redis.ZRangeBy) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRevRangeByScore(k, by)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRank(ctx context.Context, key string, member string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRank(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRevRank(ctx context.Context, key string, member string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRevRank(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRem(ctx context.Context, key string, members []interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRem(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRemRangeByScore(ctx context.Context, key, min, max string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRemRangeByScore(k, min, max)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZRemRangeByRank(ctx context.Context, key string, start int64, stop int64) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZRemRangeByRank(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZIncr(ctx context.Context, key string, member redis.Z) *redis.FloatCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZIncr(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZIncrNX(ctx context.Context, key string, member redis.Z) *redis.FloatCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZIncrNX(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZIncrXX(ctx context.Context, key string, member redis.Z) *redis.FloatCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZIncrXX(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZIncrBy(ctx context.Context, key string, increment float64, member string) *redis.FloatCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZIncrBy(k, increment, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZScore(ctx context.Context, key string, member string) *redis.FloatCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZScore(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd {
	k := m.fixKey(key)
	rcmd := m.client.ZScan(k, cursor, match, count)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LIndex(ctx context.Context, key string, index int64) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.LIndex(k, index)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.LInsert(k, op, pivot, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LLen(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.LLen(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LPop(ctx context.Context, key string) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.LPop(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LPush(ctx context.Context, key string, values ...interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.LPush(k, values...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LPushX(ctx context.Context, key string, value interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.LPushX(k, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LRange(ctx context.Context, key string, start, stop int64) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.LRange(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LRem(ctx context.Context, key string, count int64, value interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.LRem(k, count, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LSet(ctx context.Context, key string, index int64, value interface{}) *redis.StatusCmd {
	k := m.fixKey(key)
	rcmd := m.client.LSet(k, index, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) LTrim(ctx context.Context, key string, start, stop int64) *redis.StatusCmd {
	k := m.fixKey(key)
	rcmd := m.client.LTrim(k, start, stop)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) RPop(ctx context.Context, key string) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.RPop(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) RPush(ctx context.Context, key string, values ...interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.RPush(k, values...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) RPushX(ctx context.Context, key string, value interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.RPushX(k, value)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) TTL(ctx context.Context, key string) *redis.DurationCmd {
	k := m.fixKey(key)
	rcmd := m.client.TTL(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ScriptLoad(ctx context.Context, script string) *redis.StringCmd {
	rcmd := m.client.ScriptLoad(script)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) ScriptExists(ctx context.Context, scriptHash string) *redis.BoolSliceCmd {
	rcmd := m.client.ScriptExists(scriptHash)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) Eval(ctx context.Context, script string, keys []string, args ...interface{}) *redis.Cmd {
	for i, key := range keys {
		keys[i] = m.fixKey(key)
	}
	rcmd := m.client.Eval(script, keys, args...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) EvalSha(ctx context.Context, scriptHash string, keys []string, args ...interface{}) *redis.Cmd {
	for i, key := range keys {
		keys[i] = m.fixKey(key)
	}
	rcmd := m.client.EvalSha(scriptHash, keys, args...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *redis.ScanCmd {
	k := m.fixKey(key)
	rcmd := m.client.SScan(k, cursor, match, count)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SAdd(ctx context.Context, key string, members ...interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.SAdd(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SPop(ctx context.Context, key string) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.SPop(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SPopN(ctx context.Context, key string, count int64) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.SPopN(k, count)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SRem(ctx context.Context, key string, members ...interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.SRem(k, members...)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SCard(ctx context.Context, key string) *redis.IntCmd {
	k := m.fixKey(key)
	rcmd := m.client.SCard(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SIsMember(ctx context.Context, key string, member interface{}) *redis.BoolCmd {
	k := m.fixKey(key)
	rcmd := m.client.SIsMember(k, member)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SMembers(ctx context.Context, key string) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.SMembers(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SRandMember(ctx context.Context, key string) *redis.StringCmd {
	k := m.fixKey(key)
	rcmd := m.client.SRandMember(k)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SRandMemberN(ctx context.Context, key string, count int64) *redis.StringSliceCmd {
	k := m.fixKey(key)
	rcmd := m.client.SRandMemberN(k, count)
	m.setSpanTags(ctx, rcmd)
	return rcmd
}

func (m *Client) SInter(ctx context.Context, keys ...string) *redis.StringSliceCmd {
	var fixKeys = make([]string, 0, len(keys))
	for _, v := range keys {
		fixKeys = append(fixKeys, m.fixKey(v))
	}
	ssCmd := m.client.SInter(fixKeys...)
	m.setSpanTags(ctx, ssCmd)
	return ssCmd
}

func (m *Client) PFAdd(ctx context.Context, key string, els ...interface{}) *redis.IntCmd {
	k := m.fixKey(key)
	icmd := m.client.PFAdd(k, els...)
	m.setSpanTags(ctx, icmd)
	return icmd
}

func (m *Client) PFMerge(ctx context.Context, dest string, keys ...string) *redis.StatusCmd {
	var fixKeys = make([]string, 0, len(keys))
	for _, v := range keys {
		fixKeys = append(fixKeys, m.fixKey(v))
	}
	fixDest := m.fixKey(dest)
	scmd := m.client.PFMerge(fixDest, fixKeys...)
	m.setSpanTags(ctx, scmd)
	return scmd
}

func (m *Client) PFCount(ctx context.Context, keys ...string) *redis.IntCmd {
	var fixKeys = make([]string, 0, len(keys))
	for _, v := range keys {
		fixKeys = append(fixKeys, m.fixKey(v))
	}
	icmd := m.client.PFCount(fixKeys...)
	m.setSpanTags(ctx, icmd)
	return icmd
}

func (m *Client) Close(ctx context.Context) error {
	return m.client.Close()
}

func (m *Client) Pipeline() *Pipeline {
	return &Pipeline{
		namespace: m.namespace,
		pipeline:  m.client.Pipeline(),
		opts:      m.opts,
		cluster:   m.cluster,
	}
}
